using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml;
using System.Net;
using System.IO;
using System.Web.Services.Protocols;
using System;
using Microsoft.Crm.Sdk.Proxy;

using Microsoft.Win32;


namespace CRM_Perf_BenchMark.DMUnitTest
{
    /// <summary>
    /// Summary description for DMUnitTest1
    /// </summary>
    [TestClass]
    public class DuplicateRuleUnitTests
    {
        CRMEntity m_user;
        string m_userName = string.Empty;
        string m_domain = string.Empty;
        string m_password = string.Empty;
        string m_organization = string.Empty;
        string m_crmticket = string.Empty;
        NetworkCredential m_nc = null;
        CrmService Proxy = null;
        string m_CrmServer = string.Empty;
        string m_ApiServer = string.Empty;

        public TestContext testContextInstance1;

        public TestContext TestContext
        {
            get
            {
                return testContextInstance1;
            }
            set
            {
                testContextInstance1 = value;
            }
        }

        // Need this public property for webservice so that we do not use the default credential
        public System.Net.NetworkCredential NetworkCredential
        {
            get
            {
                return m_nc;
            }
            set
            {
                m_nc = value;
            }
        }

        private static string resultDir = Directory.GetDirectoryRoot(Directory.GetCurrentDirectory()) + @"PerfResult";

        public DuplicateRuleUnitTests()
        {

        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            //this is the directory where all perf results are stored
            string perfResultDir = Directory.GetDirectoryRoot(Directory.GetCurrentDirectory()) + @"PerfResult";
            if (!Directory.Exists(perfResultDir))
                Directory.CreateDirectory(perfResultDir);
        }
        
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }


        
        // Use TestInitialize to run code before running each test 
        [TestInitialize()]
        public void TestInitialize()
        {
            System.Collections.Hashtable userProps = new System.Collections.Hashtable();

            if (TestContext.Properties.Contains("UserRole"))
            {
                userProps.Add("role", (string)TestContext.Properties["UserRole"]);
            }

            m_user = EntityManager.Instance.GetNextUser(userProps);
            Proxy = new CrmService();
                        
            string[] splits = m_user["domainname"].Split(new char[] { '\\' }, 2, StringSplitOptions.RemoveEmptyEntries);
            if (splits.Length > 1)
            {
                m_domain = splits[0];
                m_userName = splits[1];
            }

            m_CrmServer = m_user["CrmServer"];
            m_ApiServer = m_user["ApiServer"];
            m_password = m_user["userpassword"];
            m_organization = m_user["organizationname"];

            if (!SetupWebService())
            {
                System.Diagnostics.Trace.WriteLine("Setup Web Service did not succeed");
            }

        }

        public bool SetupWebService()
        {
            Proxy.Url = m_ApiServer + "/MSCRMServices/2007/CrmService.asmx";

            Proxy.UnsafeAuthenticatedConnectionSharing = true;
            m_nc = new NetworkCredential(m_userName, m_password, m_domain);
            Proxy.ConnectionGroupName = m_domain + "\\" + m_userName;
            Proxy.Credentials = m_nc;

            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.OrganizationName = m_organization;
            
            token.AuthenticationType = AuthenticationType.AD;
                        
            Proxy.CrmAuthenticationTokenValue = token;
            return true;
        }

        // Use TestCleanup to run code after each test has run
        [TestCleanup()]
        public void TestCleanup()
        {
            EntityManager.Instance.FreeEntity(m_user);
            Proxy = null;
        }

        #endregion

        #region Individual Test Cases

        [TestMethod()]
        public void DMUnitTest_CrossEntityRuleCreate()
        {
            Guid ruleId = CreateDuplicateRule("Rule4");
            DeactivateRule(ruleId);
        }

        [TestMethod()]
        public void DMUnitTest_PublishSingleEntityRuleOnContacts()
        {
            PerfTestPublishRuleForAGivenRuleName("Rule5");
        }

        [TestMethod()]
        public void DMUnitTest_PublishCrossEntityRuleOnContactsAndAccount()
        {
            PerfTestPublishRuleForAGivenRuleName("Rule4");
        }

        [TestMethod()]
        public void DMUnitTest_BulkDetectDuplicates()
        {
            try
            {
                QueryExpression query = new QueryExpression();
                query.EntityName = EntityName.contact.ToString();

                //submit job to async
                DateTime startTime = DateTime.UtcNow;
                string SummaryFile = resultDir + @"\BulkDetectDuplicate_" + startTime.Ticks.ToString() + ".log";
                Guid asyncJobID = BulkDetectDup(query);

                int countOfRecords = GetQueryResultSetSize(query, 1000);

                StreamWriter logWriter = new StreamWriter(SummaryFile);
                lock (logWriter)
                {
                    logWriter.WriteLine("Logging test reult for Bulk De-dup ...");
                    logWriter.WriteLine("-----------------------------------------");
                    logWriter.WriteLine("Bulk de-dup job id " + asyncJobID);
                    logWriter.WriteLine("Duplicate detection performed for {0} records", countOfRecords);
                    logWriter.WriteLine("Test started at " + startTime);
                    logWriter.Flush();
                    logWriter.Close();
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e.Message);
                throw e;
            }
        }

        #endregion

        #region Private Methodes

        private void PerfTestPublishRuleForAGivenRuleName(string Rulename)
        {
            Guid dupruleID = Guid.Empty;
            try
            {
                DateTime oDateTime = System.DateTime.UtcNow;
                string timestamp = oDateTime.ToString();
                string randomnametoappend = oDateTime.Millisecond.ToString();
                timestamp = timestamp.Replace('/', '_');
                timestamp = timestamp.Replace(':', '_');
                string SummaryFile = resultDir + @"\PerfRulePublishSummary" + timestamp + ".log";

                DateTime before = System.DateTime.Now;

                dupruleID = CreateDuplicateRule(Rulename);
                Guid rulePublishJobId = PublishRule(dupruleID);

                string baseEntityName = GetBaseEntityNameFromRuleName(Rulename);
                int recordCountForBaseEntity = GetBaseEntityRecordCount(baseEntityName, 1000);

                StreamWriter logWriter = new StreamWriter(SummaryFile);
                lock (logWriter)
                {
                    logWriter.WriteLine("Logging test reult for Single EntityRule Publish ...");
                    logWriter.WriteLine("----------------------------------------------------");
                    logWriter.WriteLine("Rule publish job id " + rulePublishJobId);
                    logWriter.WriteLine("Test started at " + oDateTime);
                    logWriter.Flush();
                    logWriter.Close();
                }

            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e.Message);
                throw e;
            }
        }

        private int GetQueryResultSetSize(QueryExpression query, int pageSize)
        {
            BusinessEntityCollection entityCollection;
            query.PageInfo = new PagingInfo();
            query.PageInfo.Count = pageSize;
            int lastPageSize = 0, lastPageIndex = 0;
            for (int i = 1; ; i++)
            {
                query.PageInfo.PageNumber = i;
                entityCollection = Proxy.RetrieveMultiple(query);
                if (!entityCollection.MoreRecords)
                {
                    lastPageSize = entityCollection.BusinessEntities.Length;
                    lastPageIndex = i;
                    break;
                }
            }
            return (((lastPageIndex - 1) * pageSize) + lastPageSize);
        }

        private int GetBaseEntityRecordCount(string baseEntityName, int pageSize)
        {
            QueryExpression entityQuery = new QueryExpression();
            entityQuery.EntityName = baseEntityName;

            return GetQueryResultSetSize(entityQuery, pageSize);
        }

        private string GetBaseEntityNameFromRuleName(string ruleNodeName)
        {
            string configDirKeyPath = "SOFTWARE\\Microsoft\\MSCRM";
            string configDir = null;

            if (Registry.LocalMachine.OpenSubKey(configDirKeyPath) != null)
            {
                RegistryKey regKey = Registry.LocalMachine.OpenSubKey(configDirKeyPath);
                configDir = (string)regKey.GetValue("CRM_Perf_Toolkit_ConfigDir", "c:\\crmstress");
            }
            else
            {
                //setting the default directory of configsettings.xml to c:\crmstress folder
                configDir = "c:\\crmstress";
            }

            XmlDocument doc = new XmlDocument();
            doc.Load(configDir + "\\DuplicateRules_Perf.xml");

            XmlElement root = doc.DocumentElement;
            XmlNode rulenode = root.SelectSingleNode("/DuplicateRules/" + ruleNodeName);
            XmlNode ruleAttribList = rulenode.FirstChild;
            string baseEnt = ruleAttribList.Attributes.GetNamedItem("baseentity").InnerText;

            return baseEnt;
        }

        private Guid CreateDuplicateRule(string ruleNodeName)
        {
            string configDirKeyPath = "SOFTWARE\\Microsoft\\MSCRM";
            string configDir = null;

            if (Registry.LocalMachine.OpenSubKey(configDirKeyPath) != null)
            {
                RegistryKey regKey = Registry.LocalMachine.OpenSubKey(configDirKeyPath);
                configDir = (string)regKey.GetValue("CRM_Perf_Toolkit_ConfigDir", "c:\\crmstress");
            }
            else
            {
                //setting the default directory of configsettings.xml to c:\crmstress folder
                configDir = "c:\\crmstress";
            }

            XmlDocument doc = new XmlDocument();
            doc.Load(configDir + "\\DuplicateRules_Perf.xml");

            XmlElement root = doc.DocumentElement;
            XmlNode rulenode = root.SelectSingleNode("/DuplicateRules/" + ruleNodeName);
            XmlNode ruleAttribList = rulenode.FirstChild;
            string name, desc, baseEnt, crossEnt, baseAttrib, crossAttrib, param, casesen, opercode;
            DuplicateRuleOperator dupruleoper = new DuplicateRuleOperator();
            duplicaterule entity = new duplicaterule();
            duplicaterulecondition[] rulecondn = null;
            name = ruleAttribList.Attributes.GetNamedItem("name").InnerText;
            entity.name = name.Equals("") ? null : name;

            desc = ruleAttribList.Attributes.GetNamedItem("desc").InnerText;
            entity.description = desc.Equals("") ? null : desc;

            System.Diagnostics.Trace.WriteLine("Creating Rule:" + entity.description);

            baseEnt = ruleAttribList.Attributes.GetNamedItem("baseentity").InnerText;
            entity.baseentityname = baseEnt.Equals("") ? null : baseEnt;

            crossEnt = ruleAttribList.Attributes.GetNamedItem("matchingentity").InnerText;
            entity.matchingentityname = crossEnt.Equals("") ? null : crossEnt;

            casesen = ruleAttribList.Attributes.GetNamedItem("operatorcase").InnerText;
            entity.iscasesensitive = casesen.Equals("") ? null : CrmTypes.CreateCrmBoolean((bool)Convert.ToBoolean(casesen));

            WhoAmIRequest uReq = new WhoAmIRequest();
            WhoAmIResponse uResp = (WhoAmIResponse)Proxy.Execute(uReq);

            entity.ownerid = new Owner();
            entity.ownerid.Value = uResp.UserId;
            entity.ownerid.type = EntityName.systemuser.ToString();

            Guid ruleid = Guid.Empty;
            int totalTimeToCreateRule = 0;
            try
            {
                DateTime before = DateTime.Now;
                ruleid = Proxy.Create(entity);
                DateTime after = DateTime.Now;
                totalTimeToCreateRule += Convert.ToInt32(((TimeSpan)(after - before)).TotalMilliseconds);
                System.Diagnostics.Trace.WriteLine("DuplicateRule " + ruleid + " created");
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine("SoapException when creating duplicaterule :" + e.ToString());
                return Guid.Empty;
            }

            XmlNode ruleconditions = ruleAttribList.NextSibling;

            if (ruleconditions != null && ruleconditions.HasChildNodes)
            {
                rulecondn = new duplicaterulecondition[ruleconditions.ChildNodes.Count];
                for (int i = 0; i < ruleconditions.ChildNodes.Count; i++)
                {
                    rulecondn[i] = new duplicaterulecondition();

                    baseAttrib = ruleconditions.ChildNodes[i].Attributes.GetNamedItem("baseattribute").InnerText;
                    rulecondn[i].baseattributename = baseAttrib.Equals("") ? null : baseAttrib;

                    crossAttrib = ruleconditions.ChildNodes[i].Attributes.GetNamedItem("matchingattribute").InnerText;
                    rulecondn[i].matchingattributename = crossAttrib.Equals("") ? null : crossAttrib;

                    opercode = ruleconditions.ChildNodes[i].Attributes.GetNamedItem("operatorcode").InnerText;
                    switch (opercode)
                    {
                        case "Equals":
                            dupruleoper = DuplicateRuleOperator.Equals;
                            break;
                        case "FirstN":
                            dupruleoper = DuplicateRuleOperator.FirstN;
                            break;
                        case "LastN":
                            dupruleoper = DuplicateRuleOperator.LastN;
                            break;
                        case "EqualsDateOnly":
                            dupruleoper = DuplicateRuleOperator.EqualsDateOnly;
                            break;
                        case "EqualsDateAndTime":
                            dupruleoper = DuplicateRuleOperator.EqualsDateAndTime;
                            break;

                    }
                    rulecondn[i].operatorcode = opercode.Equals("") ? null : CrmTypes.CreatePicklist((int)dupruleoper);
                    param = ruleconditions.ChildNodes[i].Attributes.GetNamedItem("operatorparam").InnerText;
                    rulecondn[i].operatorparam = param.Equals("") ? null : CrmTypes.CreateCrmNumber((int)Convert.ToInt32(param));

                    rulecondn[i].regardingobjectid = new Lookup();
                    rulecondn[i].regardingobjectid.Value = ruleid;
                    rulecondn[i].regardingobjectid.type = EntityName.duplicaterule.ToString();
                    try
                    {
                        DateTime before = DateTime.Now;
                        Guid condid = Proxy.Create(rulecondn[i]);
                        DateTime after = DateTime.Now;
                        totalTimeToCreateRule += Convert.ToInt32(((TimeSpan)(after - before)).TotalMilliseconds);
                        System.Diagnostics.Trace.WriteLine("DuplicateRule Condition " + condid + " created");
                    }
                    catch (Exception e)
                    {
                        System.Diagnostics.Trace.WriteLine("Exception creating DuplicateRuleCondition :" + e.ToString());
                    }
                }
            }

            DateTime startTime = DateTime.Now;
            string SummaryFile = resultDir + @"\DuplicateRuleCreate_" + startTime.Ticks.ToString() + ".log";

            StreamWriter logWriter = new StreamWriter(SummaryFile);
            lock (logWriter)
            {
                logWriter.WriteLine("Logging test reult for Rule Create ...");
                logWriter.WriteLine("--------------------------------------");
                logWriter.WriteLine("Rule Create took {0} milisecond", totalTimeToCreateRule);
                logWriter.Flush();
                logWriter.Close();
            }

            return ruleid;
        }
     /// <summary>
     /// 
     /// </summary>
     /// <param name="dupruleID">Rule id to be published</param>
     /// <returns>Rule publish job id</returns>
        private Guid PublishRule(Guid dupruleID)
        {
            //publish rule
            PublishDuplicateRuleRequest reqPublish = new PublishDuplicateRuleRequest();
            reqPublish.DuplicateRuleId = dupruleID;
            PublishDuplicateRuleResponse resPublish = (PublishDuplicateRuleResponse)Proxy.Execute(reqPublish);
            System.Diagnostics.Trace.WriteLine("Publishing Rule with an async job");
            System.Diagnostics.Trace.WriteLine("Returned Job Id by Async Job: " + resPublish.JobId.ToString("B"));

            return resPublish.JobId;
        }

        private Guid BulkDetectDup(QueryExpression query)
        {
            BulkDetectDuplicatesRequest request = new BulkDetectDuplicatesRequest();
            request.Query = query;
            request.JobName = "PerfJob_" + System.DateTime.Now.Millisecond.ToString();
            request.SendEmailNotification = false;
            request.TemplateId = Guid.Empty;
            request.ToRecipients = new Guid[0];
            request.CCRecipients = new Guid[0];
            request.RecurrencePattern = string.Empty;
            request.RecurrenceStartTime = CrmDateTime.Now;

            BulkDetectDuplicatesResponse response = (BulkDetectDuplicatesResponse)Proxy.Execute(request);
            System.Diagnostics.Trace.WriteLine("Bulk detect duplicates returned async job id: " + response.JobId.ToString("B"));

            //poll async job for completion
            System.Diagnostics.Trace.WriteLine("Polling Async job for completion");
            asyncoperation asyncjob = null;
            ColumnSet cs = new ColumnSet(new string[] { "statecode" });
            asyncjob = (asyncoperation)Proxy.Retrieve("asyncoperation", response.JobId, cs);
            System.Diagnostics.Trace.WriteLine("Async Job State is " + asyncjob.statecode.Value);

            return response.JobId;
        }

        private void DeactivateRule(Guid dupruleID)
        {
            System.Diagnostics.Trace.WriteLine("Deleting the Rule " + dupruleID.ToString());
            Proxy.Delete("duplicaterule", dupruleID);
            System.Diagnostics.Trace.WriteLine("Rule Deleted");
        }


        internal Guid[] CreateDuplicateRules(string[] ruleNodeNames)
        {
            Guid[] rulePublishJobIds = new Guid[ruleNodeNames.Length];
            for (int i = 0; i < ruleNodeNames.Length; i++)
            {
                rulePublishJobIds[i] = CreateDuplicateRule(ruleNodeNames[i]);
            }

            return rulePublishJobIds;
        }


        #endregion
    }    
}
